'''
Created on 2022年4月21日

@author: jerry
'''

#!/usr/bin/python
# encoding: utf-8

import subprocess

import logging.handlers
import json
import requests
import time
import threading
import datetime
import base64
import os
import configparser
import re
import signal
import requests

# import check_pcie_width

import tarfile
import socket

api_url = ""
api_key = ""
'''
api 请求间隔
'''
apiRequestInterval=60

root_path = "/root/scripts/xintou-server-check/src/client/"

fdPath = '/root/FD/629-24287-XXXX-FLD-42658' 


'''
载配置文件
'''
def loadConfig():

    global api_url
    global api_key
    global apiRequestInterval

    config_file=root_path+"config.ini"

    # if os.path.exists("/scriptsdata/config.ini") == False:
    #     config_file="/scriptsdata/stone-server-check/src/client/config.ini"

    try:

        config = configparser.ConfigParser()
        config.read(config_file)

        api_url = config["DEFAULT"]['api_url']
        api_key = config["DEFAULT"]['api_key']

        if "api_request_interval" in config["DEFAULT"]:
            apiRequestInterval = int(config["DEFAULT"]["api_request_interval"])

    except Exception as e:
        printLog("加载配置文件[%s]失败，err=%s"% (config_file,e))
        exit(1)


'''
输出日志到控制台
'''
def startLogging():
    global logger
    logger = logging.getLogger("MspServer")
    logger.setLevel(level ="DEBUG")
    handler = logging.handlers.RotatingFileHandler("/tmp/server-check.log", mode='a', maxBytes=102400, backupCount=3, encoding=None, delay=False)
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    printLog("信投服务器检测平台")

def startLogging1():
    global logger1
    logger1 = logging.getLogger("MspServer1")
    logger1.setLevel(level ="DEBUG")

    # 定义日志文件基础名称和备份目录
    log_file_base = "/tmp/xintou-check"
    log_file = f"{log_file_base}.log"

    # 检查日志文件是否存在，如果存在则重命名
    if os.path.exists(log_file):
        timestamp = time.strftime("%Y%m%d_%H%M%S")
        new_log_file = f"{log_file_base}_{timestamp}.log"
        os.rename(log_file, new_log_file)

    handler1 = logging.handlers.RotatingFileHandler(log_file, mode='w', maxBytes=1024000, backupCount=3, encoding=None, delay=False)
    handler1.setLevel(logging.INFO)
    # formatter1 = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    formatter1 = logging.Formatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
    handler1.setFormatter(formatter1)
    logger1.addHandler(handler1)
    printLog1("XINTOU TEST START")

def printLog(data,level="info"):
    print("%s %s"%(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),data))
    logger.info(data)

def printLogError(data,level="error"):
    print("%s %s"%(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),data))
    logger.error(data)

def printLog1(data,level="info"):
    logger1.info(data)

def printLogError1(data,level="error"):
    logger1.error(data)


class myThread (threading.Thread):

    def __init__(self, *args):
        threading.Thread.__init__(self)
        self.category = args[0]
        self.cycle = args[1]
        # 要传的参数
        self.params = list(args)
        self.params.pop(0)
        printLog(f"初始化[{self.category}]开始，参数={self.params}, {args}")
        
        # self.category = args[0]
        # self.name = "Thread-%s"%self.category
        # self.cycle = args[1]
        # self.detectionType = args[2]

    def run(self):
        printLog("启动 %s测试，时长=%s(分钟)" %(self.category,self.cycle))

        printLog1("TEST/%s [%s] START" %(getTestMethod(self.category), self.category))
        # 2024-11-18 14:15:59 - INFO:  TEST/STRESS-NG [CPU] PASS  [Runtime: 60/60 Min]
        runTest(self.category, self.params)

def getTestMethod(category):
        match category:
            case "GPU":
                return "FIELDIAG"
            case "CPU":
                return "STRESS-NG"
            case "RAM":
                return "STRESS-NG"
            case "DISK":
                return "DISK-FIO"
            case "SENSORS":
                return "SENSORS"
            case default:
                return "DEFAULT"
            
def runTest(category, params):
    global reportData
    scriptName = "test%s"%category

    ##
    params[0] = str(int(params[0]) * 60)
    code,stdout,stderr = runScripts(scriptName, params)
    if len(stderr) == 0:
        result_data = splitResultToDict(stdout)

        reportData[category] = result_data
        printLog("%s 测试结果, %s" % (category,result_data))

        # # 硬盘FIO
        # 2024-11-18 13:15:57 - INFO:  TEST/DISK-FIO [sda] Vendor:XX / PN:XX / SN:XX ,Read:500MB/s - Write: 200MB/s
        # 2024-11-18 13:15:57 - INFO:  TEST/DISK-FIO [sdb] Vendor:XX / PN:XX / SN:XX ,Read:500MB/s - Write: 200MB/s
        # 2024-11-18 13:15:57 - INFO:  TEST/DISK-FIO [nvme0] Vendor:XX / PN:XX / SN:XX ,Read:7000MB/s - Write: 3000MB/s
        # 2024-11-18 13:15:57 - INFO:  TEST/DISK-FIO [nvme1] Vendor:XX / PN:XX / SN:XX ,Read:7000MB/s - Write: 3000MB/s

        runMin = str(int(params[0]) / 60)
        if "GPU" == category:
            result_item = result_data[0]
            msg = "，" + str(result_item["resultmsg"]) if str(result_item["resultmsg"]) != "" else "";
            realTime = "{:.2f}".format(int(result_item["runtime"]) / 60)
            if 'resultcode' in result_item and int(result_item['resultcode']) == 0:
                # 检测通过
                printLog1("TEST/%s [%s] PASS [Runtime: %s/%s Min %s]" %(getTestMethod(category), category, realTime, runMin, msg))
            else:
                printLogError1("TEST/%s [%s] FAIL [Runtime: %s/%s Min %s]" %(getTestMethod(category), category, realTime, runMin, msg))
                # TODO 如果失败则关机  延时100s
                sendFile("newTestReport", systemSerialNumber, fdPath+"/fieldiag.log", "FAIL")
                printLog("日志上传成功，"+fdPath+"/fieldiag.log"+"，总体测试结果:FAIL，服务器关机操作！")
                # os.system("shutdown /s /t 100")   # TODO 生产时打开
            # TODO GPU跑完后再跑下CPU  他俩不能同时跑

        if "DISK" == category:
            for diskItem in result_data:
                printLog1("TEST/%s [%s] PN:%s / SN:%s ,Read:%s - Write: %s - Size: %s" %(getTestMethod(category), diskItem['name'], diskItem['pn'], diskItem['sn'], diskItem['read-BW'] if 'read-BW' in diskItem else 'UNKNOW', diskItem['write-BW'] if 'write-BW' in diskItem else 'UNKNOW', diskItem['size']))
        elif "RAM" == category or "CPU" == category:
            result_item = result_data[0]
            realTime = "{:.2f}".format(int(result_item["runtime"]) / 60)
            if 'resultcode' in result_item and int(result_item['resultcode']) == 0:
                printLog1("TEST/%s [%s] PASS [Runtime: %s/%s Min]" %(getTestMethod(category), category, realTime, runMin))
            else:
                printLogError1("TEST/%s [%s] FAIL [Runtime: %s/%s Min]" %(getTestMethod(category), category, realTime, runMin))
        else:
            printLog1("TEST/%s [%s] PASS [Runtime: %s Min]" %(getTestMethod(category), category, runMin))
    else:
        result_data = []
        result_data.append({})
        result_data[0]["category"]=category
        result_data[0]["resultcode"]=1
        result_data[0]["resultmsg"]="run script failed. err=%s"% stderr
        printLog("测试失败，code=%s,err=%s"%(code,stderr))
        printLogError1("TEST/%s [%s] FAIL [Reason: Unexpected]" %(getTestMethod(category), category))

    
    sendTestResult(category,result_data)


def sendTestResult(category,data):
    global systemSerialNumber
    post_data = {"systemSerialNumber":systemSerialNumber, "detectionType":detectionType , "list": data}

    printLog("开始上传 %s 测试结果=%s" %(category, post_data))
    try:
        result_data = sendPost("testReport",post_data)
        printLog("%s 上传成功" %category)
    except Exception as e:
        printLog("%s 上传失败，err=%s"%(category,e))
'''
将收集到的数据发送到服务器
'''
def sendServerInfo(api,data):
    printLog("开始发送数据到接口,api=%s,data=%s" % (api,data))
    try:
        sendPost(api,data)
        printLog("发送成功")
    except Exception as e:
        printLogError("发送失败，%s" % e)

'''
获取服务器下一步的动作
'''
def getActions(systemSerialNumber):
    printLog("开始获取需要测试的内容")

    result_data = {}
    try:
        result_data = sendPost("pressureTestParts",{"systemSerialNumber":systemSerialNumber})
        printLog("获取成功,data=%s" % result_data)
        if "data" in result_data:
            return result_data
        else:
            printLog("返回数据异常.")
    except Exception as e:
        printLogError("获取失败，%s" % e)
    return result_data


def splitResultToDict( data):
    lines = []
    while True:
        line = str(data.readline(), encoding="utf-8")
        if line == '\n':
            break
        if line:
            parameter = {}
            for column in line.strip().split(";"):
                if column.find("=") > 0:
                    d = column.split('=')
                    parameter[d[0]] = d[1]
            lines.append(parameter)
        else:
            break
    return lines
def runScripts (action, params):
    '''
    get system info
    systemManufacturer=INSPUR;systemProductName=SA5112M4;systemSerialNumber=816433996
    '''
    
    param = ""
    if params != None:
        for item in params:
            param += " " + str(item)

    # commandArg = (root_path+"scripts/%s.sh" + param % (action))
    commandArg = "{}scripts/{}.sh{}".format(root_path, action, param)

    #scriptName = "echo systemManufacturer=INSPUR;systemProductName=SA5112M4;systemSerialNumber=816433996"


    try:
        p = subprocess.Popen(commandArg, shell=True, executable="/bin/bash",stdin=subprocess.PIPE, stdout=subprocess.PIPE , stderr=subprocess.PIPE)
        # stdout, stderr = p.communicate()  # 等待进程结束
        return p.returncode,p.stdout,p.stderr.readlines()
    except Exception as e:
        print(e)

def getDeviceInfo(deviceName):
    code,stdout,stderr = runScripts(deviceName, None)
    if len(stderr) != 0:
        printLogError("扫描 %s 失败,err=%s" %(deviceName,stderr))
        return []
    else:
        return splitResultToDict(stdout)

def getServiceInfo():
    printLog("开始扫描服务器硬件配置")

    serviceInfo = {}
    partsList=[]


    ## systeminfo
    returnInfo = getDeviceInfo("getSystem")
    if isinstance(returnInfo,list)  and  len(returnInfo) ==1:
        serviceInfo = dict( serviceInfo, ** returnInfo[0])

    ## cpu
    returnInfo = getDeviceInfo("getCpu")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)

    ## ram
    returnInfo = getDeviceInfo("getRam")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)
    ## nic
    returnInfo = getDeviceInfo("getNic")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)
    ## disk
    returnInfo = getDeviceInfo("getDisk")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)

    ## raid  TODO 暂时注释
    # returnInfo = getDeviceInfo("getRaidController")
    # if isinstance(returnInfo,list)  and  len(returnInfo) >0:
    #     partsList.extend(returnInfo)

    ## psu
    returnInfo = getDeviceInfo("getPSU")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)

    ## gpu
    returnInfo = getDeviceInfo("getGpu")
    if isinstance(returnInfo,list)  and  len(returnInfo) >0:
        partsList.extend(returnInfo)
    
    serviceInfo["list"] = partsList
    printLog("扫描结束")

    return serviceInfo

def startTestDevice(test_data):
    global detectionType
    # 最大测试时长，单位分钟
    cycle_max = 5
    threadList = []
    deployList = test_data["deployList"]
    detectionType = test_data["detectionType"]
    printLog("开始对服务器进行 [压力测试]")

    # tmp = False
    for categoryData in deployList:
        if str(categoryData["detection"]) == "1":
            testCycle = categoryData["cycle"] if "cycle" in categoryData else cycle_max
            testCategory = categoryData["category"]

            # print("=================", testCategory)

            # if testCategory == 'GPU':
            #     # 调用FD程序 
            #     threadList.append(myThread(testCategory+"FD", testCycle, categoryData['detectionType'] if 'detectionType' in categoryData else detectionType))
            # else:
                # CPU:  STRESS-NG [Runtime: 60 Min]
                # RAM:  STRESS-NG [Runtime: 60 Min]
                # printLog1("%s: %s [Runtime: %s Min]" %(testCategory, getTestMethod(testCategory), testCycle))
            
           # if testCategory == 'GPU':
           #     pass
           # else:
            threadList.append(myThread(testCategory, testCycle, categoryData['detectionType'] if 'detectionType' in categoryData else detectionType))
                # if not tmp:
                #     threadList.append(myThread(testCategory, testCycle, categoryData['detectionType'] if 'detectionType' in categoryData else detectionType))
                #     if testCategory == 'GPU':
                #         tmp = True



    for threadID in threadList:
        threadID.start()

    for threadID in threadList:
        threadID.join()

    printLog("服务器 [压力测试] 结束")




def sendPost(command,data,timeout=600000):
    headers = {"Content-Type": "application/json",
               "key": api_key}
    try:

        result = requests.post(url=("%s/%s"%(api_url,command)),json=data,headers=headers,timeout=timeout)
    except requests.exceptions.RequestException as e:
        printLogError("请求接口失败,api=%s,error=%s"%(command,e))
        raise Exception("请求接口失败,api=%s,error=%s"%(command,e))

    if result.status_code != 200:
        printLogError("请求接口失败,api=%s,error= http status error [%s]"%(command,result.status_code))
        raise Exception("请求接口失败,api=%s,error= http status error [%s]"%(command,result.status_code))

    result_data = result.json()
    if result_data["code"] != 1:
        printLogError("接口异常, api=%s,code=%s,error=%s"%(command,result_data["code"],result_data["msg"]))
        raise Exception("接口异常, api=%s,code=%s,error=%s"%(command,result_data["code"],result_data["msg"]))

    return result_data
def actionTest(data):
    global reportData

    reportData={}

    ##根据返回的测试要求进行测试
    startTestDevice(data)

    ##上报测试结果
    '''
    postData = {}
    postData["systemSerialNumber"] = data["systemsn"]
    postData["detectionType"] = data["detectionType"]
    postData["list"] = []
    for i in reportData:
        postData["list"].extend(reportData[i])
    sendServerInfo("testReport",postData)
    '''

    # check.main()
    printLog("执行完成.")
    printLog1("XINTOU TEST FINISH")

def quickTest(test_data):
    global reportData

    reportData={}

    global detectionType
    # 最大测试时长，单位分钟
    cycle_max = 5
    threadList = []
    # deployList = test_data["deployList"]
    detectionType = test_data["detectionType"]
    printLog("开始对服务器进行 [快速监测]")

    threadList.append(myThread("SENSORS", cycle_max, ''))

    for threadID in threadList:
        threadID.start()

    for threadID in threadList:
        threadID.join()

    printLog("服务器 [快速监测] 结束")


def logFileToBase64(fileName):
    result_data=""
    try:
        f = open(fileName,"r")
        for line in f:
            line = str(line)
            result_data = "%s\n%s" % ( result_data , line)

        if len(result_data) >0 :
            result_data = base64.b64encode(result_data)
    except Exception as e:
        printLog("读取文件失败 ,error=%s" % e)        

    return result_data

def initBMC(data):

    printLog("开始初始化BMC与BIOS")

    initStatus=2
    code = os.system(root_path+'scripts/initBMC.sh')
    if code != 0:
        initStatus=1
        printLog("执行initBMC.sh脚本失败")

    resportData={}

    resportData["systemSerialNumber"] = data["systemsn"]
    resportData["is_init_success"] = initStatus
    resportData["initialize_log"] = logFileToBase64("/tmp/init_bmc_%s.log" % data["systemsn"] )
    ## post log to erp
    sendServerInfo("Initialize",resportData)
    printLog("初始化执行完成.")


def sendFile(command, sn, filePath, press_status):
    url = f"{api_url}/{command}"
    files = None
    if filePath != None:
        files = {
            'press_file': ('fieldiag_'+sn+'.log', open(filePath, 'rb'), 'application/octet-stream')
        }
    data = {
        'sn': sn,
        'press_status': press_status
    }
    headers = {
        'key':api_key
    }
    response = requests.post(url, headers=headers, files=files, data=data)
    return response


def process_tar_file(tar_path, extract_to):
    # 解压tar文件
    with tarfile.open(tar_path, 'r') as tar:
        tar.extractall(path=extract_to)
    
    # 递归遍历解压目录，为所有文件添加可执行权限
    for root, dirs, files in os.walk(extract_to):
        for file in files:
            file_path = os.path.join(root, file)
            os.chmod(file_path, 0o755)  # 设置可执行权限

def lit_lamp():
    try:
        result = subprocess.run(
            ["ipmitool", "chassis", "identify", "240"],
            capture_output=True,
            text=True,
            timeout=300
        )
        
        if result.returncode == 0:
            printLog("成功点亮服务器指示灯，检测异常！！！")
        else:
            printLog(f"点灯命令执行失败: {result.stderr}")
            
    except FileNotFoundError:
        printLog("未找到ipmitool，请确保已安装")
    except subprocess.TimeoutExpired:
        printLog("点灯命令执行超时")
    except Exception as e:
        printLog(f"点灯发生错误: {str(e)}")

def main():
    global systemSerialNumber
    global apiRequestInterval
    '''
    第一步， 获取服务器信息
    第二步，上传服务器信息
    第三步，获取服务器的下一步信息
    第四步，关机
    '''

    testStatus=0
    startLogging()
    startLogging1()
    loadConfig()

    # systemInfo = getDeviceInfo("getSystem")
    # systemSerialNumber = systemInfo[0]["systemSerialNumber"]

    # 解压fd程序包   主机解压好
    # process_tar_file(fdPath+'.tar', '/root/FD')

    returnInfo = getDeviceInfo("getSystem")
    # 获取sn号
    systemSerialNumber = returnInfo[0]["systemSerialNumber"];
    serverIp = socket.gethostbyname(socket.gethostname())

    pressureData={}

    pressureData["sn"] = systemSerialNumber
    pressureData["ip"] = serverIp

    sendServerInfo("pressure_report", pressureData);

    serverInfo = getServiceInfo()

    # systemSerialNumber = serverInfo["systemSerialNumber"]
    ## add field
    sendServerInfo("record",serverInfo)

    while testStatus == 0:
        #获取需要测试的模块
        result_data = getActions(systemSerialNumber)
        # result_data = eval("{'code': 1, 'msg': '服务器及服务器配件检测列表！', 'time': '1752758982', 'data': {'systemBrand': 'Inventec', 'systemModel': 'P8000G6', 'systemsn': 'SIA04120001', 'order_code': None, 'detectionType': 1, 'deployList': [{'category': 'CPU', 'detection': '1', 'cycle': 30}, {'category': 'RAM', 'detection': '1', 'cycle': 30}, {'category': 'DISK', 'detection': '1', 'cycle': 5}, {'category': 'GPU', 'detection': 1, 'minGflops': 1111, 'maxTemp': 79, 'minPower': 2000, 'arpAvg': 11, 'gpuCount': 8, 'randomNum': 'b8ad22dba37529519a14980e850e62cb', 'cycle': 60}]}}");

        if bool(result_data):

            # 新的类型定义
            # detectionType  0  获取硬件信息， 1  快速监测，  2  压力测试， 3  重启,  4 initBMC,  9  退出

            if result_data["data"]["detectionType"] == 0 :
                # serverInfo = getServiceInfo()
                # sendServerInfo("record", serverInfo)
                quickTest(result_data["data"])
            if result_data["data"]["detectionType"] ==  1:
                actionTest(result_data["data"])
                # TODO DC操作 ！！！
                # pcieResult = check_pcie_width.check_pcie_width()
                # if pcieResult == 0:
                #     quickTest(result_data["data"])
                # else:
                #     pass
                    # TODO 接口还没好   上报给mes返回

                # checkRes = audit.check()
                # printLog("audit.check() 返回结果：%s" %(str(checkRes)))
                # if checkRes == -1:
                #     sendFile("newTestReport", systemSerialNumber, None, "配置核对失败")
                #     break

            if result_data["data"]["detectionType"] == 2:
                filePath = fdPath + '/fieldiag.log'   #  需要上传的报告路径

                if os.path.exists(filePath):
                    # 打开文件并读取内容
                    with open(filePath, 'r') as file:
                        lines = file.readlines()

                    # 初始化结果变量
                    final_result = 'UNKNOW'

                    # 遍历文件内容，寻找Final Result行
                    for line in lines:
                        if line.startswith('Final Result:'):
                            final_result = line.strip().split(':')[1].strip()
                            break
                    if "PASS" != final_result:
                        lit_lamp()

                    sendFile("newTestReport", systemSerialNumber, filePath, final_result)
                    printLog("日志上传成功，"+filePath+"，总体测试结果:"+final_result)

                    #  退出时关机
                    # os.system("shutdown /s /t 10")  # TODO 生产时打开 10秒后关机

                else:
                    printLog("日志上传失败，"+filePath+"，文件不存在！")
                    lit_lamp()
                    
                break
            if result_data["data"]["detectionType"] == 3:
                pass
                # # 重启电脑
                # printLog('重启服务器。。。')
                # printLog1("System reboot")
                # os.system('sudo reboot')  # 立即重启

                # lspci -vvd 10de: | grep -i lnksta
            if result_data["data"]["detectionType"] == 9:
                pass
                # audit.report()
                # 上传FD报告给MES
                # filePath = fdPath + '/fieldiag.log'   #  需要上传的报告路径

                # if os.path.exists(filePath):
                #     # 打开文件并读取内容
                #     with open(filePath, 'r') as file:
                #         lines = file.readlines()

                #     # 初始化结果变量
                #     final_result = 'UNKNOW'

                #     # 遍历文件内容，寻找Final Result行
                #     for line in lines:
                #         if line.startswith('Final Result:'):
                #             final_result = line.strip().split(':')[1].strip()
                #             break
                #     sendFile("newTestReport", systemSerialNumber, filePath, final_result)
                #     printLog("日志上传成功，"+filePath+"，总体测试结果:"+final_result)

                #     #  退出时关机
                #     # os.system("shutdown /s /t 10")  # TODO 生产时打开 10秒后关机

                # else:
                #     printLog("日志上传失败，"+filePath+"，文件不存在！")
                    
                # break

            if result_data["data"]["detectionType"] == 4:
                initBMC(result_data["data"])

        
        time.sleep(apiRequestInterval)

    # check.main()
    printLog1("XINTOU TEST ALL FINISH")
    printLog("所有测试结束.")

if __name__ == '__main__':
    main()


# sed -i 's/\r//' *.sh
# chmod +x *.sh